//---------------------------------------------------------------------
// Author: Keith Hill
//
// Description: Class to implement the Get-Hash cmdlet.
//
// Creation Date: Dec 9, 2006
//---------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Management.Automation;
using System.Security;
using System.Security.Cryptography;
using System.Text;

using Pscx.Commands.Internal;

namespace Pscx.Commands
{
    [Cmdlet(VerbsCommon.Get, PscxNouns.Hash, DefaultParameterSetName = ParameterSetPath),
     Description("Gets the hash value for the specified file or byte array via the pipeline."),
     DetailedDescription("Gets the hash value for the specified file or byte array via the pipeline.  The default hash algorithm is MD5, although you can specify other algorithms using the -Algorithm parameter (MD5, SHA1, SHA256, SHA384, SHA512 and RIPEMD160).  This cmdlet emits a HashInfo object that has properties for Path, Algorithm, HashString and Hash.")]
    public class GetHashCommand : PscxInputObjectPathCommandBase
    {
        private HashAlgorithm _hashAlgorithm;
        private List<byte> _byteInput;
        private string _algorithm = "MD5";

        private static class Algorithms
        {
            public const string MD5 = "MD5";
            public const string SHA1 = "SHA1";
            public const string SHA256 = "SHA256";
            public const string SHA512 = "SHA512";
            public const string RIPEMD160 = "RIPEMD160";
        }

        [Parameter(HelpMessage = "Specifies the hash algorithm to use.  Valid values are MD5 (default), SHA1, SHA256, SHA384, SHA512, RIPEMD160")]
        [DefaultValue("MD5")]
        [ValidateNotNullOrEmpty]
        [AcceptsWildcards(false)]
        [ValidateSet(
            Algorithms.MD5,
            Algorithms.SHA1,
            Algorithms.SHA256,
            Algorithms.SHA512,
            Algorithms.RIPEMD160
        )]
        public string Algorithm
        {
            get { return _algorithm; }
            set { _algorithm = value; }
        }

        protected virtual HashAlgorithm GetHashAlgorithm()
        {
            switch (_algorithm.ToUpperInvariant())
            {
                case Algorithms.MD5:       
                    return MD5.Create();

                case Algorithms.SHA1:      
                    return SHA1.Create();

                case Algorithms.SHA256:    
                    return SHA256.Create();

                case Algorithms.SHA512:    
                    return SHA512.Create();

                case Algorithms.RIPEMD160: 
                    return RIPEMD160.Create();
            }

            ArgumentException ex = new ArgumentException("Unrecognized algorithm");
            ThrowTerminatingError(new ErrorRecord(ex, "InvalidHashAlgorithm", ErrorCategory.InvalidArgument, _algorithm));
            return null;
        }

        protected override PscxInputObjectPathSettings InputSettings
        {
            get
            {
                PscxInputObjectPathSettings settings = base.InputSettings;
                settings.ProcessDirectoryInfoAsPath = false;
                return settings;
            }
        }

        protected override void BeginProcessing()
        {
            _byteInput = new List<byte>();
            _hashAlgorithm = GetHashAlgorithm();

            RegisterInputType<byte>(delegate(byte b)
            {
                _byteInput.Add(b);
            });

            RegisterInputType<IEnumerable<byte>>(delegate(IEnumerable<byte> bytes)
            {
                _byteInput.AddRange(bytes);
            });

            RegisterInputType<string>(delegate(string s)
            {
                _byteInput.AddRange(Encoding.Unicode.GetBytes(s));
            });

            // Dont throw on directories, just ignore them
            IgnoreInputType<DirectoryInfo>();

            base.BeginProcessing();
        }

        protected override void ProcessPath(string path)
        {
            try
            {
                using(Stream stream = File.OpenRead(path))
                {
                    WriteHash(_hashAlgorithm.ComputeHash(stream), path);
                }
            }
            catch (Exception ex)
            {
                WriteError(PscxErrorRecord.FileError(ex, path));
            }
        }

        protected override void EndProcessing()
        {
            if (_byteInput.Count > 0)
            {
                byte[] hash = _hashAlgorithm.ComputeHash(_byteInput.ToArray());
                WriteHash(hash);
            }
        }

        private void WriteHash(byte[] hash)
        {
            WriteHash(hash, null);
        }

        private void WriteHash(byte[] hash, string path)
        {
            StringBuilder strBld = new StringBuilder();
            foreach (byte b in hash)
            {
                strBld.AppendFormat("{0:X2}", b);
            }

            HashInfo hashInfo = new HashInfo(path, _algorithm.ToUpperInvariant(), strBld.ToString(), hash);
            WriteObject(hashInfo);
        }
    }
}
